﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace ESpace
{
    public class EClock : PictureBox
    {
        #region *** переменные ***************************************

        Timer tmr = new Timer();

        DateTime dt = DateTime.Now;

        // градусы 
        float aH;
        float aM;
        float aS;

        int cX = 50;
        int cY = 50;
        // часовая стрелка
        Point[] pH = new Point[4] { new Point(0, -35), new Point(8, 0), new Point(0, 8), new Point(-8, 0) };
        Point[] pM = new Point[4] { new Point(0, -45), new Point(5, 0), new Point(0, 7), new Point(-5, 0) };
        Point[] pS = new Point[2] { new Point(0, 0), new Point(0, -50) };

        // поинт при нажатии на стрелку
        Point downP = new Point(-1, -1);
        int hand = -1; // номар выделеной стрелки

        #endregion *** переменные ***************************************

        #region *** Свойства ********************************************

        public DateTime Now
        {
            get 
            {
                if (pShowSecond) return dt;
                return dt.AddSeconds(-dt.Second);
            }
            set
            {
                if (hand == -1 && !Tick)
                {
                    dt = value;
                    CalcGrad();
                    DrawAll();
                }
                //if(hand==0)throw new Exception("!");
            }
        }

        public DateTime Today
        {
            get { return dt.Date; }
        }

        bool pShowAmPm = true;
        public bool ShowAmPm { get { return pShowAmPm; } set { pShowAmPm = value; DrawAll(); } }
        bool pShowTime = true;
        public bool ShowTime { get { return pShowTime; } set { pShowTime = value; DrawAll(); } }
        bool pShowDate = true;
        public bool ShowDate { get { return pShowDate; } set { pShowDate = value; DrawAll(); } }
        bool pShowSecond = true;
        public bool ShowSecond { get { return pShowSecond; } set { pShowSecond = value; DrawAll(); } }

        public bool Tick { get { return tmr.Enabled; } set { tmr.Enabled = value; } }

        bool pEnabled = true;
        public new bool Enabled
        {
            get { return pEnabled; }
            set { pEnabled = value; DrawDial(); Refresh(); }
        }

        /*protected override void OnEnabledChanged(EventArgs e)
        {
            MessageBox.Show("!!!");
            DrawDial();
            base.OnEnabledChanged(e);
        }*/

        #endregion *** Свойства ********************************************

        private void timer1_Tick(object sender, EventArgs e)
        {
            dt = DateTime.Now;
            CalcGrad();
            DrawAll();
        }

        // конструктор
        public EClock()
            : base()
        {
            Width = 100;
            Height = 100;
            MinimumSize = new System.Drawing.Size(100, 100);
            MaximumSize = new System.Drawing.Size(100, 100);
            CalcGrad();
            BackgroundImage = new Bitmap(Width,Height);
            DrawDial();
            DrawAll();

            tmr.Enabled = false;
            tmr.Interval = 200;
            tmr.Tick += new EventHandler(timer1_Tick);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        // выставить новые значения градусов для стрелок
        void CalcGrad()
        {
            // количество миллисекунд
            double mill = dt.TimeOfDay.TotalMilliseconds;

            // часы am/pm, минуты, секунды
            double h = mill / 3600000.0;
            if (h >= 12) h -= 12;
            double m = (mill - (dt.Hour * 3600000)) / 60000.0;
            double s = (mill - (dt.Hour * 3600000) - (dt.Minute * 60000)) / 1000.0;

            // градусы 
            aH = (float)(360.0 / 12.0 * (h));
            aM = (float)(360.0 / 60.0 * (m));
            aS = (float)(360.0 / 60.0 * (s));
        }

        // DownPoint на стрелке (-1 - нет 0 - секундная 1 - минутная 2 - часовая)
        int GetSelect(Point DownPoint)
        {
            if (Tick) return -1;

            Point[] p = TransformPoints((float)aS, pS);

            if (pShowSecond)
                if (PointInLine(DownPoint, p[0].X, p[0].Y, p[1].X, p[1].Y, 1))
                {

                    return 0;
                }

            p = TransformPoints((float)aM, pM);
            if (PointInPoly(p, DownPoint))
            {
                return 1;
            }

            p = TransformPoints((float)aH, pH);
            if (PointInPoly(p, DownPoint))
            {
                return 2;
            }
            return -1;
        }

        #region *** рисование *********************************************

        // трансформировать полигон в зависимости от угла поворота
        Point[] TransformPoints(float ang, Point[] p)
        {
            Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
            m.RotateAt(ang, new Point(cX, cY));
            m.Translate(cX, cY);
            Point[] ret = (Point[])p.Clone();
            m.TransformPoints(ret);
            return ret;
        }

        void DrawAll()
        {
            /*if (bFirst)
            {
                bFirst = false;
                //CalcGrad();
            }*/

            if (Enabled)
            {
                /*brFace = Brushes.Lavender;
                brText = Brushes.Navy;*/
            }
            else
            {
                /*brFace = new SolidBrush(Color.FromArgb(230, 230, 230));
                brText = Brushes.Silver;*/
            }

            if (Image == null)
            {
                Image = new Bitmap(Width, Height);
            }

            Graphics gr = Graphics.FromImage(Image);

            gr.Clear(Color.Transparent);

            // дата и время
            if (pShowDate)
            {
                gr.DrawString(dt.ToString("dd.MM.yy"), Font, Brushes.Black, 28, 60);
            }
            if (pShowTime)
            {
                if(pShowSecond)
                    gr.DrawString(dt.ToString("HH:mm:ss"), Font, Brushes.Black, 28, 75);
                else
                    gr.DrawString(dt.ToString("HH:mm"), Font, Brushes.Black, 35, 75);
            }
            if (pShowAmPm)
            {
                gr.DrawRectangle(Pens.Black, 75, 46, 17, 13);
                if (dt.Hour < 12)
                    gr.DrawString("am", Font, Brushes.Black, 75, 45);
                else
                    gr.DrawString("pm", Font, Brushes.Black, 75, 45);
            }
            
            // часовая стрелка
            Point[] p = TransformPoints((float)aH, pH);
            // рисуем
            if (hand == 2) gr.FillPolygon(Brushes.Wheat, p);
            else gr.FillPolygon(Brushes.Thistle, p);
            gr.DrawPolygon(Pens.Navy, p);

            // минутная стрелка
            p = TransformPoints((float)aM, pM);
            // рисуем
            if (hand == 1) gr.FillPolygon(Brushes.Wheat, p);
            else gr.FillPolygon(Brushes.Thistle, p);
            gr.DrawPolygon(Pens.Navy, p);
   
            if (pShowSecond)
            {
                // секундная стрелка
                p = TransformPoints((float)aS, pS);
                if (hand == 0)
                {
                    gr.DrawLine(Pens.SlateBlue, p[0].X, p[0].Y, p[1].X, p[1].Y);
                }
                else
                    gr.DrawLine(Pens.Navy, p[0].X, p[0].Y, p[1].X, p[1].Y);
            }
            // центр
            gr.FillRectangle(Brushes.Navy, cX - 3, cY - 3, 6, 6);
            gr.Dispose();
            
            Refresh();
        }

        // перерисовать циферблат
        void DrawDial()
        {
            Graphics gr = Graphics.FromImage(BackgroundImage);
            gr.Clear(Color.Transparent);
            if (pEnabled)
                gr.FillEllipse(SystemBrushes.Window, 0, 0, Width, Height);
            else
                gr.FillEllipse(SystemBrushes.Control, 0, 0, Width, Height);
            for (int i = 0; i < 60; i++)
            {
                SetMatr(gr, i * 6);
                if (i % 5 == 0) gr.FillRectangle(Brushes.Blue, 47, -1, 3, 3);
                else gr.FillRectangle(Brushes.Blue, 48, 0, 1, 1);
            }
            gr.ResetTransform();
            gr.Dispose();
        }

        // повернуть матрицу рисования (для рисования циферблата)
        void SetMatr(Graphics gr, float ang)
        {
            Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
            m.RotateAt(ang, new Point(cX, cY));
            m.Translate(cX, cY);
            gr.Transform = m;
        }

        // точка на линии
        bool PointInLine(Point p, int x1, int y1, int x2, int y2, int diff)
        {
            // если по y точка находится между точками линии
            bool PointBetweenY = (y1 <= p.Y && y2 > p.Y) || (y2 <= p.Y) && (y1 > p.Y);
            // смотрим положение по x методом пропорций
            int n = (y2 - y1);
            int X = x1;
            if (n != 0) X = (x2 - x1) * (p.Y - y1) / n + x1;

            // если по x точка находится между точками линии
            bool PointBetweenX = (x1 <= p.X && x2 > p.X) || (x2 <= p.X) && (x1 > p.X);
            if (!PointBetweenX && !PointBetweenY) return false;
            // смотрим положение по y методом пропорций
            n = (x2 - x1);
            int Y = y1;
            if (n != 0) Y = (y2 - y1) * (p.X - x1) / n + y1;

            return Math.Abs(p.X - X) <= diff || Math.Abs(p.Y - Y) <= diff;

        }

        // точка в полигоне
        public static bool PointInPoly(Point[] pts, Point p)
        {
            int j;
            bool Result = false;
            j = pts.Length - 1;
            for (int i = 0; i < pts.Length; i++)
            {
                if (((pts[i].Y <= p.Y) && (p.Y < pts[j].Y)) || ((pts[j].Y <= p.Y) && (p.Y < pts[i].Y)))
                    if ((p.X < (pts[j].X - pts[i].X) * (p.Y - pts[i].Y) / (pts[j].Y - pts[i].Y) + pts[i].X))
                        Result = !Result;
                j = i;
            }
            return Result;
        }

        #endregion *** рисование *********************************************

        void ReSetTime(Point actP)
        {
            float angN;
            // вычисляем угол
            double K1 = Math.Abs(actP.Y - cY);
            double K2 = Math.Abs(actP.X - cX);
            double Hyp = Math.Sqrt(K1 * K1 + K2 * K2);
            if (Hyp == 0) angN = 0;
            else angN = (float)(Math.Acos(K1 / Hyp) * 180.0 / Math.PI);

            // 2-я четверть
            if ((actP.X - cX) >= 0 && (actP.Y - cY) >= 0)
            {
                angN = 180 - angN;
            }
            // 3-я четверть
            if ((actP.X - cX) < 0 && (actP.Y - cY) >= 0)
            {
                angN = 180 + angN;
            }
            // 4-я четверть
            if ((actP.X - cX) < 0 && (actP.Y - cY) < 0)
            {
                angN = 360 - angN;
            }

            // назад
            float N = angN - aS;
            if (hand == 1) N = angN - aM;
            if (hand == 2)N = angN - aH;

            if (N < -180) N += 360;
            if (N > 180) N -= 360;

            if (hand == 0)
            {
                // переводим в милисекунды
                double ms = N / 6.0 * 1000.0;
                dt = dt.AddMilliseconds(ms);
            }
            if (hand == 1)
            {
                // переводим в милисекунды
                double ms = N * 10000.0;
                dt = dt.AddMilliseconds(ms);
            }
            if (hand == 2)
            {
                // переводим в милисекунды
                double ms = N * 30000.0;
                dt = dt.AddMilliseconds(ms);
            }

        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (!pEnabled) return;

            hand = GetSelect(e.Location);
            if (hand != -1)
            {
                downP = e.Location;
                DrawAll();
            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            hand = -1;
            DrawAll();
            base.OnMouseUp(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (!pEnabled) return;

            if (hand != -1)
            {
                ReSetTime(e.Location);
                CalcGrad();
                DrawAll();
            }
            base.OnMouseMove(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            hand = -1;
            DrawAll();
            base.OnMouseLeave(e);
        }

    }


}
